conflicted::conflict_prefer("year", "lubridate")
[conflicted] Removing existing preference.[conflicted] Will prefer lubridate::year over any other package.
conflicted::conflict_prefer("month", "lubridate")
[conflicted] Will prefer lubridate::month over any other package.
acidentes <- arrow::read_parquet(here("data", "nomes_marotos", "acidentes.parquet"))
condutores <- arrow::read_parquet(here("data", "nomes_marotos", "condutores.parquet"))
passageiros <- arrow::read_parquet(here("data", "nomes_marotos", "passageiros.parquet"))
peoes <- arrow::read_parquet(here("data", "nomes_marotos", "peoes.parquet"))
list(acidentes, condutores, passageiros, peoes)
[[1]]

[[2]]

[[3]]

[[4]]
NA

Dados rápidos sobre os dados, pra ter uma noção do que temos

# skim them
# lapply(dfs, \(x) skimr::skim(x))

Podemos obervar várias coisas: * Colunas com tipos errados (especialmente de caracther) pelo qual vamos ter de corrigir; * As últimas colunas parecem ser dummies de grupo estário, mas com valores de máximo não esperado; Depois de resolver estes problemas, vai ser feita uma análise de cada coluna individualmente e da sua importância à priori.

# join year to every table
acidentes %<>% mutate(year = year(datetime))
condutores %<>% left_join(acidentes %>% select(idAcidente, year), by = "idAcidente")
passageiros %<>% left_join(acidentes %>% select(idAcidente, year), by = "idAcidente")
peoes %<>% left_join(acidentes %>% select(idAcidente, year), by = "idAcidente")
# check if dummies are valid
condutores %>% 
  select(starts_with("GE")) %>% 
  reframe(n_idades = rowSums(.)) %>% 
  dplyr::count(n_idades, sort = T) %>%
  mutate(perc = round(n / sum(n), 4))
        
# for the others too
passageiros %>% 
  select(starts_with("GE")) %>% 
  reframe(n_idades = rowSums(.)) %>% 
  dplyr::count(n_idades, sort = T) %>% 
  mutate(perc = round(n / sum(n), 4))

peoes %>%
  select(starts_with("GE")) %>% 
  reframe(n_idades = rowSums(.)) %>% 
  dplyr::count(n_idades, sort = T) %>% 
  mutate(perc = round(n / sum(n), 4))
NA

As idades dos condutores estão poucas sem idades, e muito poucas com multiplas idades. Não há nenhuma idade de passageiro em falta, mas há algumas com multiplas idades. As idades dos peões estão todas certas.

condutores %>% select(starts_with("GE")) %>% names
 [1] "GE-Contudor_menosOu5"   "GE-Contudor_entre6e9"   "GE-Contudor_entre10e14" "GE-Contudor_entre15e17"
 [5] "GE-Contudor_entre18e20" "GE-Contudor_entre21e24" "GE-Contudor_entre25e29" "GE-Contudor_entre30e34"
 [9] "GE-Contudor_entre35e39" "GE-Contudor_entre40e44" "GE-Contudor_entre45e49" "GE-Contudor_entre50e54"
[13] "GE-Contudor_entre55e59" "GE-Condutor_entre65e69" "GE-Condutor_entre70e74" "GE-Condutor_maisOu75"  
[17] "GE-Condutor_naoDef"    

Há uma categoria missing (GE-Condutor_entre60e64), vamos assumir que ser esses que são 0, e aqueles que são 2 vamos

# options: [1] "GE-Contudor_menosOu5"   "GE-Contudor_entre6e9"   "GE-Contudor_entre10e14"
#  [4] "GE-Contudor_entre15e17" "GE-Contudor_entre18e20" "GE-Contudor_entre21e24"
#  [7] "GE-Contudor_entre25e29" "GE-Contudor_entre30e34" "GE-Contudor_entre35e39"
# [10] "GE-Contudor_entre40e44" "GE-Contudor_entre45e49" "GE-Contudor_entre50e54"
# [13] "GE-Contudor_entre55e59" "GE-Condutor_entre65e69" "GE-Condutor_entre70e74"
# [16] "GE-Condutor_maisOu75"   "GE-Condutor_naoDef"

condutores %>% 
  mutate(n_idades = select(., starts_with("GE")) %>% rowSums()) %>% 
  mutate(`GE-Condutor_entre60e64` = if_else(n_idades == 0, T, F)) %>%
  # change where true to name of col if True
  mutate(grupo_etario = 
    fcase(
      `GE-Contudor_menosOu5`, "< 5",
      `GE-Contudor_entre6e9`, "6 - 9",
      `GE-Contudor_entre10e14`, "10 - 14",
      `GE-Contudor_entre15e17`, "15 - 17",
      `GE-Contudor_entre18e20`, "18 - 20",
      `GE-Contudor_entre21e24`, "21 - 24",
      `GE-Contudor_entre25e29`, "25 - 29",
      `GE-Contudor_entre30e34`, "30 - 34",
      `GE-Contudor_entre35e39`, "35 - 39",
      `GE-Contudor_entre40e44`, "40 - 44",
      `GE-Contudor_entre45e49`, "45 - 49",
      `GE-Contudor_entre50e54`, "50 - 54",
      `GE-Contudor_entre55e59`, "55 - 59",
      `GE-Condutor_entre60e64`, "60 - 64",
      `GE-Condutor_entre65e69`, "65 - 69",
      `GE-Condutor_entre70e74`, "70 - 74",
      `GE-Condutor_maisOu75`, "> 75",
      `GE-Condutor_naoDef`, NA_character_,
      default = NA_character_
    ) %>% factor(levels = c("< 5", "6 - 9", "10 - 14", "15 - 17", "18 - 20", "21 - 24", "25 - 29", "30 - 34", "35 - 39", "40 - 44", "45 - 49", "50 - 54", "55 - 59", "60 - 64", "65 - 69", "70 - 74", "> 75"))
  ) %>% 
  select(-n_idades, -starts_with("GE")) -> condutoresGE

condutoresGE %>% dplyr::count(grupo_etario) %>% arrange(grupo_etario) %>% mutate(perc = round(n / sum(n), 4))
NA
# a mesma coisa para os peoes e passageiros
passageiros %>% 
  mutate(n_idades = select(., starts_with("GE")) %>% rowSums()) %>% 
  mutate(`GE-Passageiro_entre60e64` = if_else(n_idades == 0, T, F)) %>%
  # change where true to name of col if True
  mutate(grupo_etario = 
    fcase(
      `GE-Passageiro_menosOu5`, "< 5",
      `GE-Passageiro_entre6e9`, "6 - 9",
      `GE-Passageiro_entre10e14`, "10 - 14",
      `GE-Passageiro_entre15e17`, "15 - 17",
      `GE-Passageiro_entre18e20`, "18 - 20",
      `GE-Passageiro_entre21e24`, "21 - 24",
      `GE-Passageiro_entre25e29`, "25 - 29",
      `GE-Passageiro_entre30e34`, "30 - 34",
      `GE-Passageiro_entre35e39`, "35 - 39",
      `GE-Passageiro_entre40e44`, "40 - 44",
      `GE-Passageiro_entre45e49`, "45 - 49",
      `GE-Passageiro_entre50e54`, "50 - 54",
      `GE-Passageiro_entre55e59`, "55 - 59",
      `GE-Passageiro_entre60e64`, "60 - 64",
      `GE-Passageiro_entre65e69`, "65 - 69",
      `GE-Passageiro_entre70e74`, "70 - 74",
      `GE-Passageiro_maisOu75`, "> 75",
      `GE-Passageiro_naoDef`, NA_character_,
      default = NA_character_
    ) %>% factor(levels = c("< 5", "6 - 9", "10 - 14", "15 - 17", "18 - 20", "21 - 24", "25 - 29", "30 - 34", "35 - 39", "40 - 44", "45 - 49", "50 - 54", "55 - 59", "60 - 64", "65 - 69", "70 - 74", "> 75"))
  ) %>%
  select(-n_idades, -starts_with("GE")) -> passageirosGE

passageirosGE %>% dplyr::count(grupo_etario) %>% arrange(grupo_etario) %>% mutate(perc = round(n / sum(n), 4))

peoes %>% 
  mutate(n_idades = select(., starts_with("GE")) %>% rowSums()) %>% 
  mutate(`GE-Peão_entre60e64` = if_else(n_idades == 0, T, F)) %>%
  # change where true to name of col if True
  mutate(grupo_etario = 
    fcase(
      `GE-Peao_menosOu5`, "< 5",
      `GE-Peao_entre6e9`, "6 - 9",
      `GE-Peao_entre10e14`, "10 - 14",
      `GE-Peao_entre15e17`, "15 - 17",
      `GE-Peao_entre18e20`, "18 - 20",
      `GE-Peao_entre21e24`, "21 - 24",
      `GE-Peao_entre25e29`, "25 - 29",
      `GE-Peao_entre30e34`, "30 - 34",
      `GE-Peao_entre35e39`, "35 - 39",
      `GE-Peao_entre40e44`, "40 - 44",
      `GE-Peao_entre45e49`, "45 - 49",
      `GE-Peao_entre50e54`, "50 - 54",
      `GE-Peao_entre55e59`, "55 - 59",
      `GE-Peao_entre60e64`, "60 - 64",
      `GE-Peao_entre65e69`, "65 - 69",
      `GE-Peao_entre70e74`, "70 - 74",
      `GE-Peao_maisOu75`, "> 75",
      `GE-Peao_naoDef`, NA_character_,
      default = NA_character_
    ) %>% factor(levels = c("< 5", "6 - 9", "10 - 14", "15 - 17", "18 - 20", "21 - 24", "25 - 29", "30 - 34", "35 - 39", "40 - 44", "45 - 49", "50 - 54", "55 - 59", "60 - 64", "65 - 69", "70 - 74", "> 75"))
  ) %>%
  select(-n_idades, -starts_with("GE")) -> peoesGE

peoesGE %>% dplyr::count(grupo_etario) %>% arrange(grupo_etario) %>% mutate(perc = round(n / sum(n), 4))
# TODO aqui um grafico de barras ficava bem
condutoresF <- condutoresGE %>% 
  mutate(
    anoMatricula = ifelse(anoMatricula < 1900, NA_integer_, anoMatricula),
    lesaoCondutor = lesaoCondutor %>% fct_relevel("Ileso", "Ferido leve", "Ferido grave", "Morto"),
    # categoriaVeiculo tem desconhecido spearado de n definido
    feztesteAlcol   = if_else(testeAlcool %>% str_starts("Submetido"), T, F),
    eraEspecial     = if_else(is.na(tipoVeiculoEspecial), F, T),
    deficienciaPneu = if_else(estadoPneus %>% str_starts("Com"), T, F),
    tinhaSeguro     = if_else(seguroVeiculo %>% str_starts("Não"), F, T),
  ) %>% 
    group_by(idAcidente) %>% 
    mutate(idCondutor = row_number(idAcidente)) %>% 
    ungroup() %>% 
    relocate(idAcidente, idCondutor) %>%
    arrange(idCondutor) %>% arrange(idAcidente) %>% 
    select(-year)
condutoresF %>% 
  select(feztesteAlcol, eraEspecial, deficienciaPneu, tinhaSeguro) %>% 
  summary
 feztesteAlcol   eraEspecial     deficienciaPneu tinhaSeguro   
 Mode :logical   Mode :logical   Mode :logical   Mode:logical  
 FALSE:32974     FALSE:493329    FALSE:485889    TRUE:494471   
 TRUE :462176    TRUE :2653      TRUE :2132      NA's:1511     
 NA's :832                       NA's :7961                    
condutoresF
acidentes %>% dplyr::count(condEstrada) %>% arrange(desc(n)) %>% mutate(perc = round(n / sum(n), 4))
acidentes$interseccao %>% table() %>% prop.table() %>% round(3)
.
         Fora da intersecção                   Em rotunda                Em cruzamento 
                       0.705                        0.042                        0.093 
            Em entroncamento         Em via de aceleração   Em ramo de ligação - saída 
                       0.138                        0.007                        0.002 
     Em via de desaceleração         Em passagem de nível Em ramo de ligação - entrada 
                       0.007                        0.001                        0.006 
acidentesF <- acidentes %>% 
  mutate(
    condEstradaLumped = condEstrada %>% fct_lump_n(1),
    GNR_PSP = GNR_PSP %>% as_factor(),
  ) %>% 
  select(-year)
acidentesF %>% dplyr::count(condEstradaLumped) %>% arrange(desc(n)) %>% mutate(perc = round(n / sum(n), 4))
passageirosGE %>% left_join(acidentesF, by = "idAcidente") %>% filter(distrito.x != distrito.y | concelho.x != concelho.y) %>% nrow
[1] 0

Os distritos dos passageiros é onde aconteceu, não de onde eles são

passageirosF <- passageirosGE %>% 
  select(-GNR_PSP, -distrito, -concelho, -year) %>% 
  relocate(idAcidente, idPassageiro) %>%
  arrange(idPassageiro) %>% arrange(idAcidente) 
passageirosF
peoesGE %>% left_join(acidentesF, by = "idAcidente") %>% filter(distrito.x != distrito.y | concelho.x != concelho.y) %>% nrow
[1] 0

Mesma coisa com os peões

peoesF <- peoesGE %>% 
  group_by(idAcidente) %>% 
  mutate(idPeao = row_number(idAcidente)) %>%
  ungroup() %>%
  relocate(idAcidente, idPeao) %>%
  arrange(idPeao) %>% arrange(idAcidente) %>%
  select(-year, -GNR_PSP, -distrito, -concelho)
peoesF

Npassageiros Npeoes Nveiculos

passageirosF %>% 
  select(idAcidente, idPassageiro) %>% 
  group_by(idAcidente) %>%
  summarise(nPassageiros = n()) -> nPassageiros
peoesF %>% 
  select(idAcidente, idPeao) %>% 
  group_by(idAcidente) %>%
  summarise(nPeoes = n()) -> nPeoes
condutoresF %>%
  select(idAcidente, idCondutor) %>% 
  group_by(idAcidente) %>%
  summarise(nVeiculos = n()) -> nVeiculos
# where is NA it should become 0
acidentesFF <- acidentesF %>% 
  left_join(nPassageiros, by = "idAcidente") %>% 
  left_join(nPeoes, by = "idAcidente") %>% 
  left_join(nVeiculos, by = "idAcidente") %>% 
  mutate(
    nPassageiros = if_else(is.na(nPassageiros), 0, nPassageiros),
    nPeoes = if_else(is.na(nPeoes), 0, nPeoes),
    nVeiculos = if_else(is.na(nVeiculos), 0, nVeiculos),
  )
acidentesFF %>% arrange(desc(datetime)) %>%  select(idAcidente, datetime, nPassageiros, nPeoes, nVeiculos) 
acidentesFF %>% select(nPassageiros, nPeoes, nVeiculos) %>% skimr::skim()
── Data Summary ────────────────────────
                           Values    
Name                       Piped data
Number of rows             327384    
Number of columns          3         
_______________________              
Column type frequency:               
  numeric                  3         
________________________             
Group variables            None      

sitios com carros nulos

# eg: 20201820097

acidentesFF %>% filter(idAcidente == 20201820097) %>% t()
                   [,1]                                                                  
idAcidente         "20201820097"                                                         
datetime           "2019-12-30 18:45:00"                                                 
GNR_PSP            "PSP"                                                                 
velocidadeLocal    "50"                                                                  
velocidadeGeral    "50"                                                                  
DiaSemana          "Segunda-Feira"                                                       
Latitude           "38.7169"                                                             
Longitude          "-9.237371"                                                           
nMortos            "0"                                                                   
nFeridosGraves     "0"                                                                   
nFeridosLigeiros   "1"                                                                   
AutoEstrada_SemSep "Outra via"                                                           
condEstrada        "Seco e limpo"                                                        
distrito           "Lisboa"                                                              
concelho           "Oeiras"                                                              
freguesia          "União das freguesias de Algés, Linda-a-Velha e Cruz Quebrada-Dafundo"
povProxima         "Linda a Velha"                                                       
nomeRua            "Alameda António Sérgio"                                              
tipoVia            "Arruamento"                                                          
nomeVia            "0"                                                                   
estadoEstrada      "Em bom estado"                                                       
kmDaEstrada        NA                                                                    
condAtmosferica    "Bom tempo"                                                           
sentido            "Dois sentidos"                                                       
interseccao        "Fora da intersecção"                                                 
localOuNao         "Dentro das localidades"                                              
luminosidade       "Noite, com iluminação"                                               
marcasRua          "Com marcas - separadoras de sentido e de vias de trânsito"           
oqaconteceu        "Atropelamento com fuga"                                              
obraArte           NA                                                                    
obstaculos         "Inexistentes"                                                        
sentidoAutoEstrada NA                                                                    
sinalizacao        "Outros"                                                              
sinaisLuminosos    "A funcionar normalmente"                                             
tipoPiso           "Betuminoso"                                                          
recta_curva        "Recta"                                                               
inclinacao         "Em patamar"                                                          
bermasEstrada      "Berma pavimentada"                                                   
tipoPista          "Em plena via"                                                        
via_transito       "Direita"                                                             
condEstradaLumped  "Seco e limpo"                                                        
nPassageiros       "0"                                                                   
nPeoes             "1"                                                                   
nVeiculos          "0"                                                                   

nsei, whtv

Lat and long

# ver as latitudes e longitudes
acidentesFF %>% 
  arrange(desc(datetime)) %>% 
  #filter(distrito == "Viseu") %>% 
  select(datetime, Latitude, Longitude, GNR_PSP)

# filtrar pelos que a longitude e latitude são NA
acidentesFF %>% 
  arrange(desc(datetime)) %>% 
  #filter(distrito == "Viseu") %>% 
  filter(Longitude %>% is.na | Latitude %>% is.na) %>% 
  mutate(year = year(datetime)) %>% 
  group_by(year) %>% 
  summarise(
    n = n(), 
    prop = n / nrow(.), 
    propTotal = n / nrow(acidentesFF)
  ) %>%
  # prop by year
  left_join(
    acidentesFF %>% 
  #    filter(distrito == "Viseu") %>% 
      group_by(year = year(datetime)) %>% 
      summarise(nTotalYear = n()), 
    by = "year") %>% 
  mutate(propYear = n / nTotalYear) %>% 
  select(
    year = year,
    `N acidentes` = nTotalYear,
    `N acidentes com lat/long NAs` = n,
    `prop acidentes com lat/long NAs` = prop,
    `prop acidentes com lat/long NAs sobre o total de acidentes` = propTotal,
    `prop acidentes com lat/long NAs sobre o total de acidentes/ano` = propYear
  ) 
# a mesma coisa so que com longitude ou lat == 0

acidentesFF %>% 
  arrange(desc(datetime)) %>% 
  #filter(distrito == "Viseu") %>% 
  select(datetime, Latitude, Longitude, GNR_PSP) %>% 
  filter(Longitude == 0 | Latitude == 0)

acidentesFF %>% 
  arrange(desc(datetime)) %>% 
  #filter(distrito == "Viseu") %>% 
  filter(Longitude == 0 | Latitude == 0) %>% 
  mutate(year = year(datetime)) %>% 
  group_by(year) %>% 
  summarise(
    n = n(), 
    prop = n / nrow(.), 
    propTotal = n / nrow(acidentesFF)
  ) %>%
  # prop by year
  left_join(
    acidentesFF %>% 
      #filter(distrito == "Viseu") %>% 
      group_by(year = year(datetime)) %>% 
      summarise(nTotalYear = n()), 
    by = "year") %>% 
  mutate(propYear = n / nTotalYear) %>% 
  select(
    year = year,
    `N acidentes em Viseu` = nTotalYear,
    `N acidentes com lat/long 0` = n,
    `prop acidentes com lat/long 0` = prop,
    `prop acidentes com lat/long 0 sobre o total de acidentes` = propTotal,
    `prop acidentes com lat/long 0 sobre o total de acidentes/ano` = propYear
  )

São ainda alguns com invalidos por ano, ainda assim não se justifica excluir a coluna diretamente No entanto, há uma grande quantidade de NAs antes de 2014, ptt será melhor excluir esses anos se não se conseguir obter as localizações.

So (quase) há NAs até 2013, mas são todos GNR Os que são 0 segue um padrão estranho, mas são todos PSP Podemos usar um api para obter as localizações se tiverem os sitios dos acidentes

# https://operations.osmfoundation.org/policies/nominatim/
# nominatimlite::geo_lite("rotunda da barrosa")
acidentesFF %>%
  filter(distrito == "Viseu") %>%
  filter(
    is.na(Latitude) | 
    is.na(Longitude) | 
    Latitude == 0 |
    Longitude == 0
  ) %>% 
  select(idAcidente, datetime, Latitude, Longitude, nomeRua) -> temp
temp
# check which nomeRua is also NA
temp %>% 
  filter(nomeRua %>% is.na) %>%
  group_by(year(datetime)) %>%
  summarise(n = n(), prop = n / nrow(.), propTotal = n / nrow(acidentesFF))
# compared to all
temp %>% 
  group_by(year(datetime)) %>%
  summarise(n = n(), prop = n / nrow(.), propTotal = n / nrow(acidentesFF))

Excluindo < 2014, são só 8+5 acidentes que não têm nomeRua

# get them
acidentesFF %>% 
  filter(distrito == "Viseu" & nomeRua %>% is.na & year(datetime) >= 2014) %>% 
  filter(
    is.na(Latitude) | 
    is.na(Longitude) | 
    Latitude == 0 |
    Longitude == 0
  )

nenhum é numa rotunda.

Falta analisar aqueles fora da área de Portugal, mas estas se calhar dps de filtrar

# NAs per month of 2019
acidentesFF %>% 
  filter(year(datetime) == 2019 | year(datetime) == 2018) %>% 
  filter(
    Latitude == 0 |
    Longitude == 0
  ) %>% 
  group_by(year(datetime), month(datetime, label = T)) %>% 
  summarise(n = n())
`summarise()` has grouped output by 'year(datetime)'. You can override using the `.groups` argument.

é bue interessante que as lats e longs a == 0 de 2019 começa em dezembro de 2018 e acaba em setembro de 2019

rotundas

acidentesFF %>% 
  mutate(
    rotunda = ifelse((!is.na(interseccao) & interseccao == "Em rotunda") | (!is.na(nomeRua) & nomeRua %>% stringr::str_to_lower() %>% stringr::str_detect("rotunda")), T, F)
  ) -> rotundas
rotundas %>%  group_by(rotunda) %>% summarise(n = n(), prop = n / nrow(.))

# filtrar por viseu
rotundas %>% 
  filter(distrito == "Viseu") %>% 
  group_by(rotunda) %>% summarise(n = n(), prop = n / nrow(.), propTotal = n / nrow(acidentesFF))

São pouquinhos mas sao ainda 500-900

# get carlos lopes
rotundas %>% filter(nomeRua %>% str_to_lower() %>% str_detect("carlos lopes")) %>% filter(distrito == "Viseu")

Todas elas têm interseccao “Em rotunda”, mas cagativo


acidentesFiltradoWOLATLONG <- acidentesFF %>%
  # rotundas
    filter(
      (!is.na(interseccao) & interseccao == "Em rotunda") | 
      (!is.na(nomeRua) & nomeRua %>% stringr::str_to_lower() %>%  stringr::str_detect("rotunda"))
    ) %>%
  # tirar < 2014
    filter(year(datetime) >= 2014) %>%
  # viseu
    mutate(EmViseu = ifelse(distrito == "Viseu", T, F)) %>%
  # é 2019 (tem aquele problema das lats e long)
    mutate(Em2019 = ifelse(year(datetime) == 2019, T, F))
acidentesFiltradoWOLATLONG

agr as analises das lats e longs com os filtrados para facilitar

Fora das caixas

Só 5 fora da caixa, faz me pergunar quantas estão dentro da caixa mas erradas

há bue pontos fora das areas (10%), o que implica que há bue pontos com lat e long erradas

eu tirava as coordenadas mas sei lá, o que vou fazer é normalizar baseado nas caixas do botas

As proximas features e melhor noutro lugar?

if (TRUE) {
  write_parquets(
    "cleaned",
    acidentesFiltrado, condutoresF, passageirosF, peoesF,
    substitute = FALSE
  )
}
# check if wrote
arrow::read_parquet(here("data", "cleaned", "acidentes.parquet"))
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyfQ0KY29uZmxpY3RlZDo6Y29uZmxpY3RfcHJlZmVyKCJ5ZWFyIiwgImx1YnJpZGF0ZSIpDQpjb25mbGljdGVkOjpjb25mbGljdF9wcmVmZXIoIm1vbnRoIiwgImx1YnJpZGF0ZSIpDQoNCg0KYWNpZGVudGVzIDwtIGFycm93OjpyZWFkX3BhcnF1ZXQoaGVyZSgiZGF0YSIsICJub21lc19tYXJvdG9zIiwgImFjaWRlbnRlcy5wYXJxdWV0IikpDQpjb25kdXRvcmVzIDwtIGFycm93OjpyZWFkX3BhcnF1ZXQoaGVyZSgiZGF0YSIsICJub21lc19tYXJvdG9zIiwgImNvbmR1dG9yZXMucGFycXVldCIpKQ0KcGFzc2FnZWlyb3MgPC0gYXJyb3c6OnJlYWRfcGFycXVldChoZXJlKCJkYXRhIiwgIm5vbWVzX21hcm90b3MiLCAicGFzc2FnZWlyb3MucGFycXVldCIpKQ0KcGVvZXMgPC0gYXJyb3c6OnJlYWRfcGFycXVldChoZXJlKCJkYXRhIiwgIm5vbWVzX21hcm90b3MiLCAicGVvZXMucGFycXVldCIpKQ0KbGlzdChhY2lkZW50ZXMsIGNvbmR1dG9yZXMsIHBhc3NhZ2Vpcm9zLCBwZW9lcykNCmBgYA0KRGFkb3MgcsOhcGlkb3Mgc29icmUgb3MgZGFkb3MsIHByYSB0ZXIgdW1hIG5vw6fDo28gZG8gcXVlIHRlbW9zDQoNCg0KYGBge3J9DQojIHNraW0gdGhlbQ0KIyBsYXBwbHkoZGZzLCBcKHgpIHNraW1yOjpza2ltKHgpKQ0KYGBgDQoNClBvZGVtb3Mgb2JlcnZhciB2w6FyaWFzIGNvaXNhczogDQoqIENvbHVuYXMgY29tIHRpcG9zIGVycmFkb3MgKGVzcGVjaWFsbWVudGUgZGUgY2FyYWN0aGVyKSBwZWxvIHF1YWwgdmFtb3MgdGVyIGRlIGNvcnJpZ2lyOw0KKiBBcyDDumx0aW1hcyBjb2x1bmFzIHBhcmVjZW0gc2VyIGR1bW1pZXMgZGUgZ3J1cG8gZXN0w6FyaW8sIG1hcyBjb20gdmFsb3JlcyBkZSBtw6F4aW1vIG7Do28gZXNwZXJhZG87DQpEZXBvaXMgZGUgcmVzb2x2ZXIgZXN0ZXMgcHJvYmxlbWFzLCB2YWkgc2VyIGZlaXRhIHVtYSBhbsOhbGlzZSBkZSBjYWRhIGNvbHVuYSBpbmRpdmlkdWFsbWVudGUgZSBkYSBzdWEgaW1wb3J0w6JuY2lhIMOgIHByaW9yaS4NCg0KYGBge3J9DQojIGpvaW4geWVhciB0byBldmVyeSB0YWJsZQ0KYWNpZGVudGVzICU8PiUgbXV0YXRlKHllYXIgPSB5ZWFyKGRhdGV0aW1lKSkNCmNvbmR1dG9yZXMgJTw+JSBsZWZ0X2pvaW4oYWNpZGVudGVzICU+JSBzZWxlY3QoaWRBY2lkZW50ZSwgeWVhciksIGJ5ID0gImlkQWNpZGVudGUiKQ0KcGFzc2FnZWlyb3MgJTw+JSBsZWZ0X2pvaW4oYWNpZGVudGVzICU+JSBzZWxlY3QoaWRBY2lkZW50ZSwgeWVhciksIGJ5ID0gImlkQWNpZGVudGUiKQ0KcGVvZXMgJTw+JSBsZWZ0X2pvaW4oYWNpZGVudGVzICU+JSBzZWxlY3QoaWRBY2lkZW50ZSwgeWVhciksIGJ5ID0gImlkQWNpZGVudGUiKQ0KYGBgDQoNCg0KYGBge3J9DQojIGNoZWNrIGlmIGR1bW1pZXMgYXJlIHZhbGlkDQpjb25kdXRvcmVzICU+JSANCiAgc2VsZWN0KHN0YXJ0c193aXRoKCJHRSIpKSAlPiUgDQogIHJlZnJhbWUobl9pZGFkZXMgPSByb3dTdW1zKC4pKSAlPiUgDQogIGRwbHlyOjpjb3VudChuX2lkYWRlcywgc29ydCA9IFQpICU+JQ0KICBtdXRhdGUocGVyYyA9IHJvdW5kKG4gLyBzdW0obiksIDQpKQ0KICAgICAgICANCiMgZm9yIHRoZSBvdGhlcnMgdG9vDQpwYXNzYWdlaXJvcyAlPiUgDQogIHNlbGVjdChzdGFydHNfd2l0aCgiR0UiKSkgJT4lIA0KICByZWZyYW1lKG5faWRhZGVzID0gcm93U3VtcyguKSkgJT4lIA0KICBkcGx5cjo6Y291bnQobl9pZGFkZXMsIHNvcnQgPSBUKSAlPiUgDQogIG11dGF0ZShwZXJjID0gcm91bmQobiAvIHN1bShuKSwgNCkpDQoNCnBlb2VzICU+JQ0KICBzZWxlY3Qoc3RhcnRzX3dpdGgoIkdFIikpICU+JSANCiAgcmVmcmFtZShuX2lkYWRlcyA9IHJvd1N1bXMoLikpICU+JSANCiAgZHBseXI6OmNvdW50KG5faWRhZGVzLCBzb3J0ID0gVCkgJT4lIA0KICBtdXRhdGUocGVyYyA9IHJvdW5kKG4gLyBzdW0obiksIDQpKQ0KDQpgYGANCg0KQXMgaWRhZGVzIGRvcyBjb25kdXRvcmVzIGVzdMOjbyBwb3VjYXMgc2VtIGlkYWRlcywgZSBtdWl0byBwb3VjYXMgY29tIG11bHRpcGxhcyBpZGFkZXMuDQpOw6NvIGjDoSBuZW5odW1hIGlkYWRlIGRlIHBhc3NhZ2Vpcm8gZW0gZmFsdGEsIG1hcyBow6EgYWxndW1hcyBjb20gbXVsdGlwbGFzIGlkYWRlcy4NCkFzIGlkYWRlcyBkb3MgcGXDtWVzIGVzdMOjbyB0b2RhcyBjZXJ0YXMuDQoNCmBgYHtyfQ0KY29uZHV0b3JlcyAlPiUgc2VsZWN0KHN0YXJ0c193aXRoKCJHRSIpKSAlPiUgbmFtZXMNCmBgYA0KDQpIw6EgdW1hIGNhdGVnb3JpYSBtaXNzaW5nIChHRS1Db25kdXRvcl9lbnRyZTYwZTY0KSwgdmFtb3MgYXNzdW1pciBxdWUgc2VyIGVzc2VzIHF1ZSBzw6NvIDAsIGUgYXF1ZWxlcyBxdWUgc8OjbyAyIHZhbW9zDQoNCmBgYHtyfQ0KIyBvcHRpb25zOiBbMV0gIkdFLUNvbnR1ZG9yX21lbm9zT3U1IiAgICJHRS1Db250dWRvcl9lbnRyZTZlOSIgICAiR0UtQ29udHVkb3JfZW50cmUxMGUxNCINCiMgIFs0XSAiR0UtQ29udHVkb3JfZW50cmUxNWUxNyIgIkdFLUNvbnR1ZG9yX2VudHJlMThlMjAiICJHRS1Db250dWRvcl9lbnRyZTIxZTI0Ig0KIyAgWzddICJHRS1Db250dWRvcl9lbnRyZTI1ZTI5IiAiR0UtQ29udHVkb3JfZW50cmUzMGUzNCIgIkdFLUNvbnR1ZG9yX2VudHJlMzVlMzkiDQojIFsxMF0gIkdFLUNvbnR1ZG9yX2VudHJlNDBlNDQiICJHRS1Db250dWRvcl9lbnRyZTQ1ZTQ5IiAiR0UtQ29udHVkb3JfZW50cmU1MGU1NCINCiMgWzEzXSAiR0UtQ29udHVkb3JfZW50cmU1NWU1OSIgIkdFLUNvbmR1dG9yX2VudHJlNjVlNjkiICJHRS1Db25kdXRvcl9lbnRyZTcwZTc0Ig0KIyBbMTZdICJHRS1Db25kdXRvcl9tYWlzT3U3NSIgICAiR0UtQ29uZHV0b3JfbmFvRGVmIg0KDQpjb25kdXRvcmVzICU+JSANCiAgbXV0YXRlKG5faWRhZGVzID0gc2VsZWN0KC4sIHN0YXJ0c193aXRoKCJHRSIpKSAlPiUgcm93U3VtcygpKSAlPiUgDQogIG11dGF0ZShgR0UtQ29uZHV0b3JfZW50cmU2MGU2NGAgPSBpZl9lbHNlKG5faWRhZGVzID09IDAsIFQsIEYpKSAlPiUNCiAgIyBjaGFuZ2Ugd2hlcmUgdHJ1ZSB0byBuYW1lIG9mIGNvbCBpZiBUcnVlDQogIG11dGF0ZShncnVwb19ldGFyaW8gPSANCiAgICBmY2FzZSgNCiAgICAgIGBHRS1Db250dWRvcl9tZW5vc091NWAsICI8IDUiLA0KICAgICAgYEdFLUNvbnR1ZG9yX2VudHJlNmU5YCwgIjYgLSA5IiwNCiAgICAgIGBHRS1Db250dWRvcl9lbnRyZTEwZTE0YCwgIjEwIC0gMTQiLA0KICAgICAgYEdFLUNvbnR1ZG9yX2VudHJlMTVlMTdgLCAiMTUgLSAxNyIsDQogICAgICBgR0UtQ29udHVkb3JfZW50cmUxOGUyMGAsICIxOCAtIDIwIiwNCiAgICAgIGBHRS1Db250dWRvcl9lbnRyZTIxZTI0YCwgIjIxIC0gMjQiLA0KICAgICAgYEdFLUNvbnR1ZG9yX2VudHJlMjVlMjlgLCAiMjUgLSAyOSIsDQogICAgICBgR0UtQ29udHVkb3JfZW50cmUzMGUzNGAsICIzMCAtIDM0IiwNCiAgICAgIGBHRS1Db250dWRvcl9lbnRyZTM1ZTM5YCwgIjM1IC0gMzkiLA0KICAgICAgYEdFLUNvbnR1ZG9yX2VudHJlNDBlNDRgLCAiNDAgLSA0NCIsDQogICAgICBgR0UtQ29udHVkb3JfZW50cmU0NWU0OWAsICI0NSAtIDQ5IiwNCiAgICAgIGBHRS1Db250dWRvcl9lbnRyZTUwZTU0YCwgIjUwIC0gNTQiLA0KICAgICAgYEdFLUNvbnR1ZG9yX2VudHJlNTVlNTlgLCAiNTUgLSA1OSIsDQogICAgICBgR0UtQ29uZHV0b3JfZW50cmU2MGU2NGAsICI2MCAtIDY0IiwNCiAgICAgIGBHRS1Db25kdXRvcl9lbnRyZTY1ZTY5YCwgIjY1IC0gNjkiLA0KICAgICAgYEdFLUNvbmR1dG9yX2VudHJlNzBlNzRgLCAiNzAgLSA3NCIsDQogICAgICBgR0UtQ29uZHV0b3JfbWFpc091NzVgLCAiPiA3NSIsDQogICAgICBgR0UtQ29uZHV0b3JfbmFvRGVmYCwgTkFfY2hhcmFjdGVyXywNCiAgICAgIGRlZmF1bHQgPSBOQV9jaGFyYWN0ZXJfDQogICAgKSAlPiUgZmFjdG9yKGxldmVscyA9IGMoIjwgNSIsICI2IC0gOSIsICIxMCAtIDE0IiwgIjE1IC0gMTciLCAiMTggLSAyMCIsICIyMSAtIDI0IiwgIjI1IC0gMjkiLCAiMzAgLSAzNCIsICIzNSAtIDM5IiwgIjQwIC0gNDQiLCAiNDUgLSA0OSIsICI1MCAtIDU0IiwgIjU1IC0gNTkiLCAiNjAgLSA2NCIsICI2NSAtIDY5IiwgIjcwIC0gNzQiLCAiPiA3NSIpKQ0KICApICU+JSANCiAgc2VsZWN0KC1uX2lkYWRlcywgLXN0YXJ0c193aXRoKCJHRSIpKSAtPiBjb25kdXRvcmVzR0UNCg0KY29uZHV0b3Jlc0dFICU+JSBkcGx5cjo6Y291bnQoZ3J1cG9fZXRhcmlvKSAlPiUgYXJyYW5nZShncnVwb19ldGFyaW8pICU+JSBtdXRhdGUocGVyYyA9IHJvdW5kKG4gLyBzdW0obiksIDQpKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiMgYSBtZXNtYSBjb2lzYSBwYXJhIG9zIHBlb2VzIGUgcGFzc2FnZWlyb3MNCnBhc3NhZ2Vpcm9zICU+JSANCiAgbXV0YXRlKG5faWRhZGVzID0gc2VsZWN0KC4sIHN0YXJ0c193aXRoKCJHRSIpKSAlPiUgcm93U3VtcygpKSAlPiUgDQogIG11dGF0ZShgR0UtUGFzc2FnZWlyb19lbnRyZTYwZTY0YCA9IGlmX2Vsc2Uobl9pZGFkZXMgPT0gMCwgVCwgRikpICU+JQ0KICAjIGNoYW5nZSB3aGVyZSB0cnVlIHRvIG5hbWUgb2YgY29sIGlmIFRydWUNCiAgbXV0YXRlKGdydXBvX2V0YXJpbyA9IA0KICAgIGZjYXNlKA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fbWVub3NPdTVgLCAiPCA1IiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlNmU5YCwgIjYgLSA5IiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlMTBlMTRgLCAiMTAgLSAxNCIsDQogICAgICBgR0UtUGFzc2FnZWlyb19lbnRyZTE1ZTE3YCwgIjE1IC0gMTciLA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fZW50cmUxOGUyMGAsICIxOCAtIDIwIiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlMjFlMjRgLCAiMjEgLSAyNCIsDQogICAgICBgR0UtUGFzc2FnZWlyb19lbnRyZTI1ZTI5YCwgIjI1IC0gMjkiLA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fZW50cmUzMGUzNGAsICIzMCAtIDM0IiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlMzVlMzlgLCAiMzUgLSAzOSIsDQogICAgICBgR0UtUGFzc2FnZWlyb19lbnRyZTQwZTQ0YCwgIjQwIC0gNDQiLA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fZW50cmU0NWU0OWAsICI0NSAtIDQ5IiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlNTBlNTRgLCAiNTAgLSA1NCIsDQogICAgICBgR0UtUGFzc2FnZWlyb19lbnRyZTU1ZTU5YCwgIjU1IC0gNTkiLA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fZW50cmU2MGU2NGAsICI2MCAtIDY0IiwNCiAgICAgIGBHRS1QYXNzYWdlaXJvX2VudHJlNjVlNjlgLCAiNjUgLSA2OSIsDQogICAgICBgR0UtUGFzc2FnZWlyb19lbnRyZTcwZTc0YCwgIjcwIC0gNzQiLA0KICAgICAgYEdFLVBhc3NhZ2Vpcm9fbWFpc091NzVgLCAiPiA3NSIsDQogICAgICBgR0UtUGFzc2FnZWlyb19uYW9EZWZgLCBOQV9jaGFyYWN0ZXJfLA0KICAgICAgZGVmYXVsdCA9IE5BX2NoYXJhY3Rlcl8NCiAgICApICU+JSBmYWN0b3IobGV2ZWxzID0gYygiPCA1IiwgIjYgLSA5IiwgIjEwIC0gMTQiLCAiMTUgLSAxNyIsICIxOCAtIDIwIiwgIjIxIC0gMjQiLCAiMjUgLSAyOSIsICIzMCAtIDM0IiwgIjM1IC0gMzkiLCAiNDAgLSA0NCIsICI0NSAtIDQ5IiwgIjUwIC0gNTQiLCAiNTUgLSA1OSIsICI2MCAtIDY0IiwgIjY1IC0gNjkiLCAiNzAgLSA3NCIsICI+IDc1IikpDQogICkgJT4lDQogIHNlbGVjdCgtbl9pZGFkZXMsIC1zdGFydHNfd2l0aCgiR0UiKSkgLT4gcGFzc2FnZWlyb3NHRQ0KDQpwYXNzYWdlaXJvc0dFICU+JSBkcGx5cjo6Y291bnQoZ3J1cG9fZXRhcmlvKSAlPiUgYXJyYW5nZShncnVwb19ldGFyaW8pICU+JSBtdXRhdGUocGVyYyA9IHJvdW5kKG4gLyBzdW0obiksIDQpKQ0KDQpwZW9lcyAlPiUgDQogIG11dGF0ZShuX2lkYWRlcyA9IHNlbGVjdCguLCBzdGFydHNfd2l0aCgiR0UiKSkgJT4lIHJvd1N1bXMoKSkgJT4lIA0KICBtdXRhdGUoYEdFLVBlw6NvX2VudHJlNjBlNjRgID0gaWZfZWxzZShuX2lkYWRlcyA9PSAwLCBULCBGKSkgJT4lDQogICMgY2hhbmdlIHdoZXJlIHRydWUgdG8gbmFtZSBvZiBjb2wgaWYgVHJ1ZQ0KICBtdXRhdGUoZ3J1cG9fZXRhcmlvID0gDQogICAgZmNhc2UoDQogICAgICBgR0UtUGVhb19tZW5vc091NWAsICI8IDUiLA0KICAgICAgYEdFLVBlYW9fZW50cmU2ZTlgLCAiNiAtIDkiLA0KICAgICAgYEdFLVBlYW9fZW50cmUxMGUxNGAsICIxMCAtIDE0IiwNCiAgICAgIGBHRS1QZWFvX2VudHJlMTVlMTdgLCAiMTUgLSAxNyIsDQogICAgICBgR0UtUGVhb19lbnRyZTE4ZTIwYCwgIjE4IC0gMjAiLA0KICAgICAgYEdFLVBlYW9fZW50cmUyMWUyNGAsICIyMSAtIDI0IiwNCiAgICAgIGBHRS1QZWFvX2VudHJlMjVlMjlgLCAiMjUgLSAyOSIsDQogICAgICBgR0UtUGVhb19lbnRyZTMwZTM0YCwgIjMwIC0gMzQiLA0KICAgICAgYEdFLVBlYW9fZW50cmUzNWUzOWAsICIzNSAtIDM5IiwNCiAgICAgIGBHRS1QZWFvX2VudHJlNDBlNDRgLCAiNDAgLSA0NCIsDQogICAgICBgR0UtUGVhb19lbnRyZTQ1ZTQ5YCwgIjQ1IC0gNDkiLA0KICAgICAgYEdFLVBlYW9fZW50cmU1MGU1NGAsICI1MCAtIDU0IiwNCiAgICAgIGBHRS1QZWFvX2VudHJlNTVlNTlgLCAiNTUgLSA1OSIsDQogICAgICBgR0UtUGVhb19lbnRyZTYwZTY0YCwgIjYwIC0gNjQiLA0KICAgICAgYEdFLVBlYW9fZW50cmU2NWU2OWAsICI2NSAtIDY5IiwNCiAgICAgIGBHRS1QZWFvX2VudHJlNzBlNzRgLCAiNzAgLSA3NCIsDQogICAgICBgR0UtUGVhb19tYWlzT3U3NWAsICI+IDc1IiwNCiAgICAgIGBHRS1QZWFvX25hb0RlZmAsIE5BX2NoYXJhY3Rlcl8sDQogICAgICBkZWZhdWx0ID0gTkFfY2hhcmFjdGVyXw0KICAgICkgJT4lIGZhY3RvcihsZXZlbHMgPSBjKCI8IDUiLCAiNiAtIDkiLCAiMTAgLSAxNCIsICIxNSAtIDE3IiwgIjE4IC0gMjAiLCAiMjEgLSAyNCIsICIyNSAtIDI5IiwgIjMwIC0gMzQiLCAiMzUgLSAzOSIsICI0MCAtIDQ0IiwgIjQ1IC0gNDkiLCAiNTAgLSA1NCIsICI1NSAtIDU5IiwgIjYwIC0gNjQiLCAiNjUgLSA2OSIsICI3MCAtIDc0IiwgIj4gNzUiKSkNCiAgKSAlPiUNCiAgc2VsZWN0KC1uX2lkYWRlcywgLXN0YXJ0c193aXRoKCJHRSIpKSAtPiBwZW9lc0dFDQoNCnBlb2VzR0UgJT4lIGRwbHlyOjpjb3VudChncnVwb19ldGFyaW8pICU+JSBhcnJhbmdlKGdydXBvX2V0YXJpbykgJT4lIG11dGF0ZShwZXJjID0gcm91bmQobiAvIHN1bShuKSwgNCkpDQpgYGANCg0KYGBge3J9DQojIFRPRE8gYXF1aSB1bSBncmFmaWNvIGRlIGJhcnJhcyBmaWNhdmEgYmVtDQpgYGANCg0KYGBge3J9DQpjb25kdXRvcmVzRiA8LSBjb25kdXRvcmVzR0UgJT4lIA0KICBtdXRhdGUoDQogICAgYW5vTWF0cmljdWxhID0gaWZlbHNlKGFub01hdHJpY3VsYSA8IDE5MDAsIE5BX2ludGVnZXJfLCBhbm9NYXRyaWN1bGEpLA0KICAgIGxlc2FvQ29uZHV0b3IgPSBsZXNhb0NvbmR1dG9yICU+JSBmY3RfcmVsZXZlbCgiSWxlc28iLCAiRmVyaWRvIGxldmUiLCAiRmVyaWRvIGdyYXZlIiwgIk1vcnRvIiksDQogICAgIyBjYXRlZ29yaWFWZWljdWxvIHRlbSBkZXNjb25oZWNpZG8gc3BlYXJhZG8gZGUgbiBkZWZpbmlkbw0KICAgIGZlenRlc3RlQWxjb2wgICA9IGlmX2Vsc2UodGVzdGVBbGNvb2wgJT4lIHN0cl9zdGFydHMoIlN1Ym1ldGlkbyIpLCBULCBGKSwNCiAgICBlcmFFc3BlY2lhbCAgICAgPSBpZl9lbHNlKGlzLm5hKHRpcG9WZWljdWxvRXNwZWNpYWwpLCBGLCBUKSwNCiAgICBkZWZpY2llbmNpYVBuZXUgPSBpZl9lbHNlKGVzdGFkb1BuZXVzICU+JSBzdHJfc3RhcnRzKCJDb20iKSwgVCwgRiksDQogICAgdGluaGFTZWd1cm8gICAgID0gaWZfZWxzZShzZWd1cm9WZWljdWxvICU+JSBzdHJfc3RhcnRzKCJOw6NvIiksIEYsIFQpLA0KICApICU+JSANCiAgICBncm91cF9ieShpZEFjaWRlbnRlKSAlPiUgDQogICAgbXV0YXRlKGlkQ29uZHV0b3IgPSByb3dfbnVtYmVyKGlkQWNpZGVudGUpKSAlPiUgDQogICAgdW5ncm91cCgpICU+JSANCiAgICByZWxvY2F0ZShpZEFjaWRlbnRlLCBpZENvbmR1dG9yKSAlPiUNCiAgICBhcnJhbmdlKGlkQ29uZHV0b3IpICU+JSBhcnJhbmdlKGlkQWNpZGVudGUpICU+JSANCiAgICBzZWxlY3QoLXllYXIpDQpjb25kdXRvcmVzRiAlPiUgDQogIHNlbGVjdChmZXp0ZXN0ZUFsY29sLCBlcmFFc3BlY2lhbCwgZGVmaWNpZW5jaWFQbmV1LCB0aW5oYVNlZ3VybykgJT4lIA0KICBzdW1tYXJ5DQpjb25kdXRvcmVzRg0KYGBgDQoNCmBgYHtyfQ0KYWNpZGVudGVzICU+JSBkcGx5cjo6Y291bnQoY29uZEVzdHJhZGEpICU+JSBhcnJhbmdlKGRlc2MobikpICU+JSBtdXRhdGUocGVyYyA9IHJvdW5kKG4gLyBzdW0obiksIDQpKQ0KYGBgDQoNCmBgYHtyfQ0KYWNpZGVudGVzJGludGVyc2VjY2FvICU+JSB0YWJsZSgpICU+JSBwcm9wLnRhYmxlKCkgJT4lIHJvdW5kKDMpDQpgYGANCg0KYGBge3J9DQphY2lkZW50ZXNGIDwtIGFjaWRlbnRlcyAlPiUgDQogIG11dGF0ZSgNCiAgICBjb25kRXN0cmFkYUx1bXBlZCA9IGNvbmRFc3RyYWRhICU+JSBmY3RfbHVtcF9uKDEpLA0KICAgIEdOUl9QU1AgPSBHTlJfUFNQICU+JSBhc19mYWN0b3IoKSwNCiAgKSAlPiUgDQogIHNlbGVjdCgteWVhcikNCmFjaWRlbnRlc0YgJT4lIGRwbHlyOjpjb3VudChjb25kRXN0cmFkYUx1bXBlZCkgJT4lIGFycmFuZ2UoZGVzYyhuKSkgJT4lIG11dGF0ZShwZXJjID0gcm91bmQobiAvIHN1bShuKSwgNCkpDQpgYGANCg0KYGBge3J9DQpwYXNzYWdlaXJvc0dFICU+JSBsZWZ0X2pvaW4oYWNpZGVudGVzRiwgYnkgPSAiaWRBY2lkZW50ZSIpICU+JSBmaWx0ZXIoZGlzdHJpdG8ueCAhPSBkaXN0cml0by55IHwgY29uY2VsaG8ueCAhPSBjb25jZWxoby55KSAlPiUgbnJvdw0KYGBgDQpPcyBkaXN0cml0b3MgZG9zIHBhc3NhZ2Vpcm9zIMOpIG9uZGUgYWNvbnRlY2V1LCBuw6NvIGRlIG9uZGUgZWxlcyBzw6NvDQoNCg0KYGBge3J9DQpwYXNzYWdlaXJvc0YgPC0gcGFzc2FnZWlyb3NHRSAlPiUgDQogIHNlbGVjdCgtR05SX1BTUCwgLWRpc3RyaXRvLCAtY29uY2VsaG8sIC15ZWFyKSAlPiUgDQogIHJlbG9jYXRlKGlkQWNpZGVudGUsIGlkUGFzc2FnZWlybykgJT4lDQogIGFycmFuZ2UoaWRQYXNzYWdlaXJvKSAlPiUgYXJyYW5nZShpZEFjaWRlbnRlKSANCnBhc3NhZ2Vpcm9zRg0KYGBgDQoNCg0KYGBge3J9DQpwZW9lc0dFICU+JSBsZWZ0X2pvaW4oYWNpZGVudGVzRiwgYnkgPSAiaWRBY2lkZW50ZSIpICU+JSBmaWx0ZXIoZGlzdHJpdG8ueCAhPSBkaXN0cml0by55IHwgY29uY2VsaG8ueCAhPSBjb25jZWxoby55KSAlPiUgbnJvdw0KYGBgDQpNZXNtYSBjb2lzYSBjb20gb3MgcGXDtWVzDQoNCmBgYHtyfQ0KcGVvZXNGIDwtIHBlb2VzR0UgJT4lIA0KICBncm91cF9ieShpZEFjaWRlbnRlKSAlPiUgDQogIG11dGF0ZShpZFBlYW8gPSByb3dfbnVtYmVyKGlkQWNpZGVudGUpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICByZWxvY2F0ZShpZEFjaWRlbnRlLCBpZFBlYW8pICU+JQ0KICBhcnJhbmdlKGlkUGVhbykgJT4lIGFycmFuZ2UoaWRBY2lkZW50ZSkgJT4lDQogIHNlbGVjdCgteWVhciwgLUdOUl9QU1AsIC1kaXN0cml0bywgLWNvbmNlbGhvKQ0KcGVvZXNGDQpgYGANCg0KTnBhc3NhZ2Vpcm9zDQpOcGVvZXMNCk52ZWljdWxvcw0KDQpgYGB7cn0NCnBhc3NhZ2Vpcm9zRiAlPiUgDQogIHNlbGVjdChpZEFjaWRlbnRlLCBpZFBhc3NhZ2Vpcm8pICU+JSANCiAgZ3JvdXBfYnkoaWRBY2lkZW50ZSkgJT4lDQogIHN1bW1hcmlzZShuUGFzc2FnZWlyb3MgPSBuKCkpIC0+IG5QYXNzYWdlaXJvcw0KcGVvZXNGICU+JSANCiAgc2VsZWN0KGlkQWNpZGVudGUsIGlkUGVhbykgJT4lIA0KICBncm91cF9ieShpZEFjaWRlbnRlKSAlPiUNCiAgc3VtbWFyaXNlKG5QZW9lcyA9IG4oKSkgLT4gblBlb2VzDQpjb25kdXRvcmVzRiAlPiUNCiAgc2VsZWN0KGlkQWNpZGVudGUsIGlkQ29uZHV0b3IpICU+JSANCiAgZ3JvdXBfYnkoaWRBY2lkZW50ZSkgJT4lDQogIHN1bW1hcmlzZShuVmVpY3Vsb3MgPSBuKCkpIC0+IG5WZWljdWxvcw0KIyB3aGVyZSBpcyBOQSBpdCBzaG91bGQgYmVjb21lIDANCmFjaWRlbnRlc0ZGIDwtIGFjaWRlbnRlc0YgJT4lIA0KICBsZWZ0X2pvaW4oblBhc3NhZ2Vpcm9zLCBieSA9ICJpZEFjaWRlbnRlIikgJT4lIA0KICBsZWZ0X2pvaW4oblBlb2VzLCBieSA9ICJpZEFjaWRlbnRlIikgJT4lIA0KICBsZWZ0X2pvaW4oblZlaWN1bG9zLCBieSA9ICJpZEFjaWRlbnRlIikgJT4lIA0KICBtdXRhdGUoDQogICAgblBhc3NhZ2Vpcm9zID0gaWZfZWxzZShpcy5uYShuUGFzc2FnZWlyb3MpLCAwLCBuUGFzc2FnZWlyb3MpLA0KICAgIG5QZW9lcyA9IGlmX2Vsc2UoaXMubmEoblBlb2VzKSwgMCwgblBlb2VzKSwNCiAgICBuVmVpY3Vsb3MgPSBpZl9lbHNlKGlzLm5hKG5WZWljdWxvcyksIDAsIG5WZWljdWxvcyksDQogICkNCmFjaWRlbnRlc0ZGICU+JSBhcnJhbmdlKGRlc2MoZGF0ZXRpbWUpKSAlPiUgIHNlbGVjdChpZEFjaWRlbnRlLCBkYXRldGltZSwgblBhc3NhZ2Vpcm9zLCBuUGVvZXMsIG5WZWljdWxvcykgDQphY2lkZW50ZXNGRiAlPiUgc2VsZWN0KG5QYXNzYWdlaXJvcywgblBlb2VzLCBuVmVpY3Vsb3MpICU+JSBza2ltcjo6c2tpbSgpDQoNCmBgYA0KDQpzaXRpb3MgY29tIGNhcnJvcyBudWxvcw0KDQpgYGB7cn0NCiMgZWc6IDIwMjAxODIwMDk3DQoNCmFjaWRlbnRlc0ZGICU+JSBmaWx0ZXIoaWRBY2lkZW50ZSA9PSAyMDIwMTgyMDA5NykgJT4lIHQoKQ0KYGBgDQoNCm5zZWksIHdodHYNCg0KDQoNCkxhdCBhbmQgbG9uZw0KDQoNCmBgYHtyfQ0KIyB2ZXIgYXMgbGF0aXR1ZGVzIGUgbG9uZ2l0dWRlcw0KYWNpZGVudGVzRkYgJT4lIA0KICBhcnJhbmdlKGRlc2MoZGF0ZXRpbWUpKSAlPiUgDQogICNmaWx0ZXIoZGlzdHJpdG8gPT0gIlZpc2V1IikgJT4lIA0KICBzZWxlY3QoZGF0ZXRpbWUsIExhdGl0dWRlLCBMb25naXR1ZGUsIEdOUl9QU1ApDQoNCiMgZmlsdHJhciBwZWxvcyBxdWUgYSBsb25naXR1ZGUgZSBsYXRpdHVkZSBzw6NvIE5BDQphY2lkZW50ZXNGRiAlPiUgDQogIGFycmFuZ2UoZGVzYyhkYXRldGltZSkpICU+JSANCiAgI2ZpbHRlcihkaXN0cml0byA9PSAiVmlzZXUiKSAlPiUgDQogIGZpbHRlcihMb25naXR1ZGUgJT4lIGlzLm5hIHwgTGF0aXR1ZGUgJT4lIGlzLm5hKSAlPiUgDQogIG11dGF0ZSh5ZWFyID0geWVhcihkYXRldGltZSkpICU+JSANCiAgZ3JvdXBfYnkoeWVhcikgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgbiA9IG4oKSwgDQogICAgcHJvcCA9IG4gLyBucm93KC4pLCANCiAgICBwcm9wVG90YWwgPSBuIC8gbnJvdyhhY2lkZW50ZXNGRikNCiAgKSAlPiUNCiAgIyBwcm9wIGJ5IHllYXINCiAgbGVmdF9qb2luKA0KICAgIGFjaWRlbnRlc0ZGICU+JSANCiAgIyAgICBmaWx0ZXIoZGlzdHJpdG8gPT0gIlZpc2V1IikgJT4lIA0KICAgICAgZ3JvdXBfYnkoeWVhciA9IHllYXIoZGF0ZXRpbWUpKSAlPiUgDQogICAgICBzdW1tYXJpc2UoblRvdGFsWWVhciA9IG4oKSksIA0KICAgIGJ5ID0gInllYXIiKSAlPiUgDQogIG11dGF0ZShwcm9wWWVhciA9IG4gLyBuVG90YWxZZWFyKSAlPiUgDQogIHNlbGVjdCgNCiAgICB5ZWFyID0geWVhciwNCiAgICBgTiBhY2lkZW50ZXNgID0gblRvdGFsWWVhciwNCiAgICBgTiBhY2lkZW50ZXMgY29tIGxhdC9sb25nIE5Bc2AgPSBuLA0KICAgIGBwcm9wIGFjaWRlbnRlcyBjb20gbGF0L2xvbmcgTkFzYCA9IHByb3AsDQogICAgYHByb3AgYWNpZGVudGVzIGNvbSBsYXQvbG9uZyBOQXMgc29icmUgbyB0b3RhbCBkZSBhY2lkZW50ZXNgID0gcHJvcFRvdGFsLA0KICAgIGBwcm9wIGFjaWRlbnRlcyBjb20gbGF0L2xvbmcgTkFzIHNvYnJlIG8gdG90YWwgZGUgYWNpZGVudGVzL2Fub2AgPSBwcm9wWWVhcg0KICApIA0KIyBhIG1lc21hIGNvaXNhIHNvIHF1ZSBjb20gbG9uZ2l0dWRlIG91IGxhdCA9PSAwDQoNCmFjaWRlbnRlc0ZGICU+JSANCiAgYXJyYW5nZShkZXNjKGRhdGV0aW1lKSkgJT4lIA0KICAjZmlsdGVyKGRpc3RyaXRvID09ICJWaXNldSIpICU+JSANCiAgc2VsZWN0KGRhdGV0aW1lLCBMYXRpdHVkZSwgTG9uZ2l0dWRlLCBHTlJfUFNQKSAlPiUgDQogIGZpbHRlcihMb25naXR1ZGUgPT0gMCB8IExhdGl0dWRlID09IDApDQoNCmFjaWRlbnRlc0ZGICU+JSANCiAgYXJyYW5nZShkZXNjKGRhdGV0aW1lKSkgJT4lIA0KICAjZmlsdGVyKGRpc3RyaXRvID09ICJWaXNldSIpICU+JSANCiAgZmlsdGVyKExvbmdpdHVkZSA9PSAwIHwgTGF0aXR1ZGUgPT0gMCkgJT4lIA0KICBtdXRhdGUoeWVhciA9IHllYXIoZGF0ZXRpbWUpKSAlPiUgDQogIGdyb3VwX2J5KHllYXIpICU+JSANCiAgc3VtbWFyaXNlKA0KICAgIG4gPSBuKCksIA0KICAgIHByb3AgPSBuIC8gbnJvdyguKSwgDQogICAgcHJvcFRvdGFsID0gbiAvIG5yb3coYWNpZGVudGVzRkYpDQogICkgJT4lDQogICMgcHJvcCBieSB5ZWFyDQogIGxlZnRfam9pbigNCiAgICBhY2lkZW50ZXNGRiAlPiUgDQogICAgICAjZmlsdGVyKGRpc3RyaXRvID09ICJWaXNldSIpICU+JSANCiAgICAgIGdyb3VwX2J5KHllYXIgPSB5ZWFyKGRhdGV0aW1lKSkgJT4lIA0KICAgICAgc3VtbWFyaXNlKG5Ub3RhbFllYXIgPSBuKCkpLCANCiAgICBieSA9ICJ5ZWFyIikgJT4lIA0KICBtdXRhdGUocHJvcFllYXIgPSBuIC8gblRvdGFsWWVhcikgJT4lIA0KICBzZWxlY3QoDQogICAgeWVhciA9IHllYXIsDQogICAgYE4gYWNpZGVudGVzIGVtIFZpc2V1YCA9IG5Ub3RhbFllYXIsDQogICAgYE4gYWNpZGVudGVzIGNvbSBsYXQvbG9uZyAwYCA9IG4sDQogICAgYHByb3AgYWNpZGVudGVzIGNvbSBsYXQvbG9uZyAwYCA9IHByb3AsDQogICAgYHByb3AgYWNpZGVudGVzIGNvbSBsYXQvbG9uZyAwIHNvYnJlIG8gdG90YWwgZGUgYWNpZGVudGVzYCA9IHByb3BUb3RhbCwNCiAgICBgcHJvcCBhY2lkZW50ZXMgY29tIGxhdC9sb25nIDAgc29icmUgbyB0b3RhbCBkZSBhY2lkZW50ZXMvYW5vYCA9IHByb3BZZWFyDQogICkNCmBgYA0KDQpTw6NvIGFpbmRhIGFsZ3VucyBjb20gaW52YWxpZG9zIHBvciBhbm8sIGFpbmRhIGFzc2ltIG7Do28gc2UganVzdGlmaWNhIGV4Y2x1aXIgYSBjb2x1bmEgZGlyZXRhbWVudGUNCk5vIGVudGFudG8sIGjDoSB1bWEgZ3JhbmRlIHF1YW50aWRhZGUgZGUgTkFzIGFudGVzIGRlIDIwMTQsIHB0dCBzZXLDoSBtZWxob3IgZXhjbHVpciBlc3NlcyBhbm9zIHNlIG7Do28gc2UgY29uc2VndWlyIG9idGVyIGFzIGxvY2FsaXphw6fDtWVzLg0KDQpTbyAocXVhc2UpIGjDoSBOQXMgYXTDqSAyMDEzLCBtYXMgc8OjbyB0b2RvcyBHTlINCk9zIHF1ZSBzw6NvIDAgc2VndWUgdW0gcGFkcsOjbyBlc3RyYW5obywgbWFzIHPDo28gdG9kb3MgUFNQDQpQb2RlbW9zIHVzYXIgdW0gYXBpIHBhcmEgb2J0ZXIgYXMgbG9jYWxpemHDp8O1ZXMgc2UgdGl2ZXJlbSBvcyBzaXRpb3MgZG9zIGFjaWRlbnRlcw0KDQoNCmBgYHtyfQ0KIyBodHRwczovL29wZXJhdGlvbnMub3NtZm91bmRhdGlvbi5vcmcvcG9saWNpZXMvbm9taW5hdGltLw0KIyBub21pbmF0aW1saXRlOjpnZW9fbGl0ZSgicm90dW5kYSBkYSBiYXJyb3NhIikNCmBgYA0KDQpgYGB7cn0NCmFjaWRlbnRlc0ZGICU+JQ0KICBmaWx0ZXIoZGlzdHJpdG8gPT0gIlZpc2V1IikgJT4lDQogIGZpbHRlcigNCiAgICBpcy5uYShMYXRpdHVkZSkgfCANCiAgICBpcy5uYShMb25naXR1ZGUpIHwgDQogICAgTGF0aXR1ZGUgPT0gMCB8DQogICAgTG9uZ2l0dWRlID09IDANCiAgKSAlPiUgDQogIHNlbGVjdChpZEFjaWRlbnRlLCBkYXRldGltZSwgTGF0aXR1ZGUsIExvbmdpdHVkZSwgbm9tZVJ1YSkgLT4gdGVtcA0KdGVtcA0KIyBjaGVjayB3aGljaCBub21lUnVhIGlzIGFsc28gTkENCnRlbXAgJT4lIA0KICBmaWx0ZXIobm9tZVJ1YSAlPiUgaXMubmEpICU+JQ0KICBncm91cF9ieSh5ZWFyKGRhdGV0aW1lKSkgJT4lDQogIHN1bW1hcmlzZShuID0gbigpLCBwcm9wID0gbiAvIG5yb3coLiksIHByb3BUb3RhbCA9IG4gLyBucm93KGFjaWRlbnRlc0ZGKSkNCiMgY29tcGFyZWQgdG8gYWxsDQp0ZW1wICU+JSANCiAgZ3JvdXBfYnkoeWVhcihkYXRldGltZSkpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSwgcHJvcCA9IG4gLyBucm93KC4pLCBwcm9wVG90YWwgPSBuIC8gbnJvdyhhY2lkZW50ZXNGRikpDQpgYGANCg0KRXhjbHVpbmRvIDwgMjAxNCwgc8OjbyBzw7MgOCs1IGFjaWRlbnRlcyBxdWUgbsOjbyB0w6ptIG5vbWVSdWENCg0KYGBge3J9DQojIGdldCB0aGVtDQphY2lkZW50ZXNGRiAlPiUgDQogIGZpbHRlcihkaXN0cml0byA9PSAiVmlzZXUiICYgbm9tZVJ1YSAlPiUgaXMubmEgJiB5ZWFyKGRhdGV0aW1lKSA+PSAyMDE0KSAlPiUgDQogIGZpbHRlcigNCiAgICBpcy5uYShMYXRpdHVkZSkgfCANCiAgICBpcy5uYShMb25naXR1ZGUpIHwgDQogICAgTGF0aXR1ZGUgPT0gMCB8DQogICAgTG9uZ2l0dWRlID09IDANCiAgKQ0KYGBgDQpuZW5odW0gw6kgbnVtYSByb3R1bmRhLg0KDQoNCg0KDQpGYWx0YSBhbmFsaXNhciBhcXVlbGVzIGZvcmEgZGEgw6FyZWEgZGUgUG9ydHVnYWwsIG1hcyBlc3RhcyBzZSBjYWxoYXIgZHBzIGRlIGZpbHRyYXINCg0KYGBge3J9DQojIE5BcyBwZXIgbW9udGggb2YgMjAxOQ0KYWNpZGVudGVzRkYgJT4lIA0KICBmaWx0ZXIoeWVhcihkYXRldGltZSkgPT0gMjAxOSB8IHllYXIoZGF0ZXRpbWUpID09IDIwMTgpICU+JSANCiAgZmlsdGVyKA0KICAgIExhdGl0dWRlID09IDAgfA0KICAgIExvbmdpdHVkZSA9PSAwDQogICkgJT4lIA0KICBncm91cF9ieSh5ZWFyKGRhdGV0aW1lKSwgbW9udGgoZGF0ZXRpbWUsIGxhYmVsID0gVCkpICU+JSANCiAgc3VtbWFyaXNlKG4gPSBuKCkpDQpgYGANCg0Kw6kgYnVlIGludGVyZXNzYW50ZSBxdWUgYXMgbGF0cyBlIGxvbmdzIGEgPT0gMCBkZSAyMDE5IGNvbWXDp2EgZW0gZGV6ZW1icm8gZGUgMjAxOCBlIGFjYWJhIGVtIHNldGVtYnJvIGRlIDIwMTkNCg0Kcm90dW5kYXMNCmBgYHtyfQ0KYWNpZGVudGVzRkYgJT4lIA0KICBtdXRhdGUoDQogICAgcm90dW5kYSA9IGlmZWxzZSgoIWlzLm5hKGludGVyc2VjY2FvKSAmIGludGVyc2VjY2FvID09ICJFbSByb3R1bmRhIikgfCAoIWlzLm5hKG5vbWVSdWEpICYgbm9tZVJ1YSAlPiUgc3RyaW5ncjo6c3RyX3RvX2xvd2VyKCkgJT4lIHN0cmluZ3I6OnN0cl9kZXRlY3QoInJvdHVuZGEiKSksIFQsIEYpDQogICkgLT4gcm90dW5kYXMNCnJvdHVuZGFzICU+JSAgZ3JvdXBfYnkocm90dW5kYSkgJT4lIHN1bW1hcmlzZShuID0gbigpLCBwcm9wID0gbiAvIG5yb3coLikpDQoNCiMgZmlsdHJhciBwb3IgdmlzZXUNCnJvdHVuZGFzICU+JSANCiAgZmlsdGVyKGRpc3RyaXRvID09ICJWaXNldSIpICU+JSANCiAgZ3JvdXBfYnkocm90dW5kYSkgJT4lIHN1bW1hcmlzZShuID0gbigpLCBwcm9wID0gbiAvIG5yb3coLiksIHByb3BUb3RhbCA9IG4gLyBucm93KGFjaWRlbnRlc0ZGKSkNCmBgYA0KDQpTw6NvIHBvdXF1aW5ob3MgbWFzIHNhbyBhaW5kYSA1MDAtOTAwDQoNCmBgYHtyfQ0KIyBnZXQgY2FybG9zIGxvcGVzDQpyb3R1bmRhcyAlPiUgZmlsdGVyKG5vbWVSdWEgJT4lIHN0cl90b19sb3dlcigpICU+JSBzdHJfZGV0ZWN0KCJjYXJsb3MgbG9wZXMiKSkgJT4lIGZpbHRlcihkaXN0cml0byA9PSAiVmlzZXUiKQ0KYGBgDQoNClRvZGFzIGVsYXMgdMOqbSBpbnRlcnNlY2NhbyAiRW0gcm90dW5kYSIsIG1hcyBjYWdhdGl2bw0KDQpgYGB7cn0NCg0KYWNpZGVudGVzRmlsdHJhZG9XT0xBVExPTkcgPC0gYWNpZGVudGVzRkYgJT4lDQogICMgcm90dW5kYXMNCiAgICBmaWx0ZXIoDQogICAgICAoIWlzLm5hKGludGVyc2VjY2FvKSAmIGludGVyc2VjY2FvID09ICJFbSByb3R1bmRhIikgfCANCiAgICAgICghaXMubmEobm9tZVJ1YSkgJiBub21lUnVhICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgIHN0cl9kZXRlY3QoInJvdHVuZGEiKSkgfA0KICAgICAgIyBleGNlY2FvIHBhcmEgYSBwcmFjYSBDYXJsb3MgbG9wZXMNCiAgICAgIChkaXN0cml0byA9PSAiVmlzZXUiICYgIWlzLm5hKG5vbWVSdWEpICYgbm9tZVJ1YSAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9kZXRlY3QoImNhcmxvcyBsb3BlcyIpKQ0KICAgICkgJT4lDQogICMgdGlyYXIgPCAyMDE0DQogICAgZmlsdGVyKHllYXIoZGF0ZXRpbWUpID49IDIwMTQpICU+JQ0KICAjIHZpc2V1DQogICAgbXV0YXRlKEVtVmlzZXUgPSBpZmVsc2UoZGlzdHJpdG8gPT0gIlZpc2V1IiwgVCwgRikpICU+JQ0KICAjIMOpIDIwMTkgKHRlbSBhcXVlbGUgcHJvYmxlbWEgZGFzIGxhdHMgZSBsb25nKQ0KICAgIG11dGF0ZShFbTIwMTkgPSBpZmVsc2UoeWVhcihkYXRldGltZSkgPT0gMjAxOSwgVCwgRikpDQphY2lkZW50ZXNGaWx0cmFkb1dPTEFUTE9ORw0KYGBgDQoNCmFnciBhcyBhbmFsaXNlcyBkYXMgbGF0cyBlIGxvbmdzIGNvbSBvcyBmaWx0cmFkb3MgcGFyYSBmYWNpbGl0YXINCg0KRm9yYSBkYXMgY2FpeGFzDQpgYGB7cn0NCg0KIyBtZWxob3IgY2FpeGEgw6kgZGUgKDM2Ljk2LCAtOS41MykgYXTDqSAoNDIuMTUsIC02LjE5KQ0KYWNpZGVudGVzRmlsdHJhZG9XT0xBVExPTkcgJT4lIA0KICBmaWx0ZXIoDQogICAgTGF0aXR1ZGUgPCAzNi45NiB8DQogICAgTGF0aXR1ZGUgPiA0Mi4xNSB8DQogICAgTG9uZ2l0dWRlIDwgLTkuNTMgfA0KICAgIExvbmdpdHVkZSA+IC02LjE5DQogICkgJT4lIA0KICBmaWx0ZXIoISgNCiAgICBMYXRpdHVkZSA9PSAwIHwNCiAgICBMb25naXR1ZGUgPT0gMA0KICApKSAlPiUgDQogIHNlbGVjdChpZEFjaWRlbnRlLCBkYXRldGltZSwgTGF0aXR1ZGUsIExvbmdpdHVkZSwgbm9tZVJ1YSwgZGlzdHJpdG8pDQoNCm1pbk1heExhdExvbmdQb3JEaXN0IDwtIGFjaWRlbnRlc0ZpbHRyYWRvV09MQVRMT05HICU+JSANCiAgZmlsdGVyKA0KICAgIExhdGl0dWRlID4gMzYuOTYgfA0KICAgIExhdGl0dWRlIDwgNDIuMTUgfA0KICAgIExvbmdpdHVkZSA+IC05LjUzIHwNCiAgICBMb25naXR1ZGUgPCAtNi4xOQ0KICApICU+JSANCiAgZmlsdGVyKExhdGl0dWRlICE9IDAgJiBMb25naXR1ZGUgIT0gMCkgJT4lIA0KICBncm91cF9ieShkaXN0cml0bykgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgbWluTGF0ID0gbWluKExhdGl0dWRlLCBuYS5ybSA9IFQpLCANCiAgICBtYXhMYXQgPSBtYXgoTGF0aXR1ZGUsIG5hLnJtID0gVCksIA0KICAgIG1pbkxvbmcgPSBtaW4oTG9uZ2l0dWRlLCBuYS5ybSA9IFQpLCANCiAgICBtYXhMb25nID0gbWF4KExvbmdpdHVkZSwgbmEucm0gPSBUKQ0KICApDQptaW5NYXhMYXRMb25nUG9yRGlzdCAlPiUgYXJyYW5nZShkaXN0cml0byAlPiUgYXMuY2hhcmFjdGVyKCkpDQpgYGANClPDsyA1IGZvcmEgZGEgY2FpeGEsIGZheiBtZSBwZXJndW5hciBxdWFudGFzIGVzdMOjbyBkZW50cm8gZGEgY2FpeGEgbWFzIGVycmFkYXMNCg0KYGBge3J9DQpyZWFkeGw6OnJlYWRfZXhjZWwoaGVyZSgibGltcGV6YSIsICJkaXN0cml0b3MueGxzeCIpKSANCmBgYA0KDQpgYGB7cn0NCnJlYWR4bDo6cmVhZF9leGNlbChoZXJlKCJsaW1wZXphIiwgImRpc3RyaXRvcy54bHN4IiksIGNvbF90eXBlcyA9IGMoInRleHQiLCByZXAoIm51bWVyaWMiLCAxMikpKSAlPiUNCiAgcmVuYW1lKGRpc3RyaXRvID0gRGlzdHJpdG8pICU+JSANCiAgcm93d2lzZSgpICU+JSANCiAgbXV0YXRlKA0KICAgIGRpc3RyaXRvID0gc3RyX3NwbGl0X2ZpeGVkKGRpc3RyaXRvLCAiRGlzdHJpdG8gZFtlYW9dICIsIDIpWzJdLA0KICAgIGxhdF9taW4yID0gIGlmZWxzZShsYXRfbWluMiAgJT4lIGlzLm5hLCBsYXRfbWluLCAgIGxhdF9taW4yKSwNCiAgICBsb25nX21pbjIgPSBpZmVsc2UobG9uZ19taW4yICU+JSBpcy5uYSwgbG9uZ19taW4sICBsb25nX21pbjIpLA0KICAgIGxhdF9taW4zID0gIGlmZWxzZShsYXRfbWluMyAgJT4lIGlzLm5hLCBsYXRfbWluMiwgIGxhdF9taW4zKSwNCiAgICBsb25nX21pbjMgPSBpZmVsc2UobG9uZ19taW4zICU+JSBpcy5uYSwgbG9uZ19taW4yLCBsb25nX21pbjMpLA0KICAgIGxhdF9tYXgyID0gIGlmZWxzZShsYXRfbWF4MiAgJT4lIGlzLm5hLCBsYXRfbWF4LCAgIGxhdF9tYXgyKSwNCiAgICBsb25nX21heDIgPSBpZmVsc2UobG9uZ19tYXgyICU+JSBpcy5uYSwgbG9uZ19tYXgsICBsb25nX21heDIpLA0KICAgIGxhdF9tYXgzID0gIGlmZWxzZShsYXRfbWF4MyAgJT4lIGlzLm5hLCBsYXRfbWF4MiwgIGxhdF9tYXgzKSwNCiAgICBsb25nX21heDMgPSBpZmVsc2UobG9uZ19tYXgzICU+JSBpcy5uYSwgbG9uZ19tYXgyLCBsb25nX21heDMpDQogICkgLT4gYm94ZXMNCmJveGVzDQpgYGANCg0KDQpgYGB7cn0NCmlzSW5zaWRlIDwtIGZ1bmN0aW9uKExhdCwgTG9uZywgbWluTGF0LCBtYXhMYXQsIG1pbkxvbmcsIG1heExvbmcpIHsNCiAgTGF0ID4gbWluTGF0ICYgTGF0IDwgbWF4TGF0ICYgTG9uZyA+IG1pbkxvbmcgJiBMb25nIDwgbWF4TG9uZw0KfQ0KDQojIGNoZWNrIGlmIHRoZXkgYXJlIGluc2lkZSBmaXJzdA0KYWNpZGVudGVzRmlsdHJhZG9XT0xBVExPTkcgJT4lIA0KICBmaWx0ZXIoDQogICAgTGF0aXR1ZGUgPiAzNi45NiB8DQogICAgTGF0aXR1ZGUgPCA0Mi4xNSB8DQogICAgTG9uZ2l0dWRlID4gLTkuNTMgfA0KICAgIExvbmdpdHVkZSA8IC02LjE5DQogICkgJT4lIA0KICBmaWx0ZXIoISgNCiAgICBMYXRpdHVkZSA9PSAwIHwNCiAgICBMb25naXR1ZGUgPT0gMA0KICApKSAlPiUNCiAgbGVmdF9qb2luKGJveGVzLCBieSA9ICJkaXN0cml0byIpICU+JQ0KICBmaWx0ZXIoISgNCiAgICBpc0luc2lkZShMYXRpdHVkZSwgTG9uZ2l0dWRlLCBsYXRfbWluLCBsYXRfbWF4LCBsb25nX21pbiwgbG9uZ19tYXgpIHwgDQogICAgaXNJbnNpZGUoTGF0aXR1ZGUsIExvbmdpdHVkZSwgbGF0X21pbjIsIGxhdF9tYXgyLCBsb25nX21pbjIsIGxvbmdfbWF4MikgfCANCiAgICBpc0luc2lkZShMYXRpdHVkZSwgTG9uZ2l0dWRlLCBsYXRfbWluMywgbGF0X21heDMsIGxvbmdfbWluMywgbG9uZ19tYXgzKQ0KICApKSAlPiUgc2VsZWN0KGlkQWNpZGVudGUsIGRpc3RyaXRvLCBmcmVndWVzaWEsIG5vbWVSdWEsIExhdGl0dWRlLCBMb25naXR1ZGUsIGxhdF9taW4sIGxhdF9tYXgsIGxvbmdfbWluLCBsb25nX21heCwgTGF0aXR1ZGUsIExvbmdpdHVkZSwgbGF0X21pbjIsIGxhdF9tYXgyLCBsb25nX21pbjIsIGxvbmdfbWF4MiwgTGF0aXR1ZGUsIExvbmdpdHVkZSwgbGF0X21pbjMsIGxhdF9tYXgzLCBsb25nX21pbjMsIGxvbmdfbWF4MykgLT4gYWNpZGVudGVzQ29tQ29yZGVuYWRhc0VycmFkYXMNCg0KYWNpZGVudGVzQ29tQ29yZGVuYWRhc0VycmFkYXMNCiMgY291bnQgYW5kIHByb3ANCmFjaWRlbnRlc0NvbUNvcmRlbmFkYXNFcnJhZGFzICU+JSANCiAgZ3JvdXBfYnkoZGlzdHJpdG8pICU+JSANCiAgc3VtbWFyaXNlKA0KICAgIG4gPSBuKCksDQogICAgcHJvcCA9IG4oKSAvIChhY2lkZW50ZXNGaWx0cmFkb1dPTEFUTE9ORyAlPiUgcmVuYW1lKGRpc3RyaXRvQWNpZGVudGUgPSAiZGlzdHJpdG8iKSAlPiUgZmlsdGVyKGRpc3RyaXRvQWNpZGVudGUgPT0gZGlzdHJpdG8pICU+JSBucm93KCkpDQogICkgJT4lIGFycmFuZ2UoZGVzYyhwcm9wKSkNCg0KKGFjaWRlbnRlc0NvbUNvcmRlbmFkYXNFcnJhZGFzICU+JSBucm93KCkpIC8gKGFjaWRlbnRlc0ZpbHRyYWRvV09MQVRMT05HICU+JSBucm93KCkpICU+JSB0aWJibGUoKQ0KYGBgDQoNCmjDoSBidWUgcG9udG9zIGZvcmEgZGFzIGFyZWFzICgxMCUpLCBvIHF1ZSBpbXBsaWNhIHF1ZSBow6EgYnVlIHBvbnRvcyBjb20gbGF0IGUgbG9uZyBlcnJhZGFzDQoNCmV1IHRpcmF2YSBhcyBjb29yZGVuYWRhcyBtYXMgc2VpIGzDoSwgbyBxdWUgdm91IGZhemVyIMOpIG5vcm1hbGl6YXIgYmFzZWFkbyBuYXMgY2FpeGFzIGRvIGJvdGFzDQoNCg0KYGBge3J9DQpib3hlc01pbiA8LSBib3hlcyAlPiUNCiAgcm93d2lzZSgpICU+JQ0KICBtdXRhdGUobGF0X21pbiA9IG1pbihjKGxhdF9taW4sIGxhdF9taW4yLCBsYXRfbWluMyksIG5hLnJtID0gVCksDQogICAgICAgICBsYXRfbWF4ID0gbWF4KGMobGF0X21heCwgbGF0X21heDIsIGxhdF9tYXgzKSwgbmEucm0gPSBUKSwNCiAgICAgICAgIGxvbmdfbWluID0gbWluKGMobG9uZ19taW4sIGxvbmdfbWluMiwgbG9uZ19taW4zKSwgbmEucm0gPSBUKSwNCiAgICAgICAgIGxvbmdfbWF4ID0gbWF4KGMobG9uZ19tYXgsIGxvbmdfbWF4MiwgbG9uZ19tYXgzKSwgbmEucm0gPSBUKSkgJT4lDQogIHNlbGVjdChkaXN0cml0bywgbGF0X21pbiwgbGF0X21heCwgbG9uZ19taW4sIGxvbmdfbWF4KQ0KDQphY2lkZW50ZXNGaWx0cmFkb1dPTEFUTE9ORyAlPiUgDQogICMgbGF0IGUgbG9uZyAodGlyYXIgb3MgbGFuZyBlIGxvbmcgaW52YWxpZG9zIGEgbmFvIHNlciBxdWUgc2VqYSAyMDE5KQ0KICAgIGZpbHRlcighKA0KICAgICAgIUVtMjAxOSAmICgNCiAgICAgICAgaXMubmEoTGF0aXR1ZGUpIHwgDQogICAgICAgIGlzLm5hKExvbmdpdHVkZSkgfCANCiAgICAgICAgTGF0aXR1ZGUgPT0gMCB8DQogICAgICAgIExvbmdpdHVkZSA9PSAwDQogICAgICApDQogICAgKSkgJT4lDQogICMgbm9ybWFsaXplIGxhdCBhbmQgbG9uZw0KICAgIG11dGF0ZSgNCiAgICAgIG1pbm1heExhdFBvcnQgPSAoTGF0aXR1ZGUgLSBtaW4oLiRMYXRpdHVkZSkpIC8gKG1heCguJExhdGl0dWRlKSAtIG1pbiguJExhdGl0dWRlKSksDQogICAgICBtaW5tYXhMb25nUG9ydCA9IChMb25naXR1ZGUgLSBtaW4oLiRMb25naXR1ZGUpKSAvIChtYXgoLiRMb25naXR1ZGUpIC0gbWluKC4kTG9uZ2l0dWRlKSkNCiAgICApICU+JSANCiAgIyBqb2luIHdpdGggYm94ZXMNCiAgICBsZWZ0X2pvaW4oYm94ZXNNaW4sIGJ5ID0gImRpc3RyaXRvIikgJT4lDQogICAgbXV0YXRlKA0KICAgICAgbWlubWF4TGF0RGlzdCA9IChMYXRpdHVkZSAtIGxhdF9taW4pIC8gKGxhdF9tYXggLSBsYXRfbWluKSwNCiAgICAgIG1pbm1heExvbmdEaXN0ID0gKExvbmdpdHVkZSAtIGxvbmdfbWluKSAvIChsb25nX21heCAtIGxvbmdfbWluKQ0KICAgICkgJT4lDQogICAgc2VsZWN0KC1sYXRfbWluLCAtbGF0X21heCwgLWxvbmdfbWluLCAtbG9uZ19tYXgpICU+JQ0KICAjIHJvdW5kIGxhdHMNCiAgICBtdXRhdGUoDQogICAgICBMYXRpdHVkZUFwcm94ID0gcm91bmQoTGF0aXR1ZGUsIDIpLA0KICAgICAgTG9uZ2l0dWRlQXByb3ggPSByb3VuZChMb25naXR1ZGUsIDIpLA0KICAgICAgbWlubWF4TGF0UG9ydCA9IHJvdW5kKG1pbm1heExhdFBvcnQsIDIpLA0KICAgICAgbWlubWF4TG9uZ1BvcnQgPSByb3VuZChtaW5tYXhMb25nUG9ydCwgMiksDQogICAgICBtaW5tYXhMYXREaXN0ID0gcm91bmQobWlubWF4TGF0RGlzdCwgMiksDQogICAgICBtaW5tYXhMb25nRGlzdCA9IHJvdW5kKG1pbm1heExvbmdEaXN0LCAyKQ0KICAgICkgJT4lDQogICAgc2VsZWN0KC1rbURhRXN0cmFkYSwgLXNlbnRpZG9BdXRvRXN0cmFkYSkgJT4lDQogICMgc2Ugw6kgY2FwaXRhbA0KICAgIG11dGF0ZShpc0NhcGl0YWwgPSBkaXN0cml0byA9PSBjb25jZWxobykgLT4gYWNpZGVudGVzRmlsdHJhZG8NCmFjaWRlbnRlc0ZpbHRyYWRvDQpgYGANCg0KQXMgcHJveGltYXMgZmVhdHVyZXMgZSBtZWxob3Igbm91dHJvIGx1Z2FyPw0KDQpgYGB7cn0NCmlmIChUUlVFKSB7DQogIHdyaXRlX3BhcnF1ZXRzKA0KICAgICJjbGVhbmVkIiwNCiAgICBhY2lkZW50ZXNGaWx0cmFkbywgY29uZHV0b3Jlc0YsIHBhc3NhZ2Vpcm9zRiwgcGVvZXNGLA0KICAgIHN1YnN0aXR1dGUgPSBGQUxTRQ0KICApDQp9DQpgYGANCg0KYGBge3J9DQojIGNoZWNrIGlmIHdyb3RlDQphcnJvdzo6cmVhZF9wYXJxdWV0KGhlcmUoImRhdGEiLCAiY2xlYW5lZCIsICJhY2lkZW50ZXMucGFycXVldCIpKQ0KYGBgDQoNCg==